package de.hsb.ai.util.map;

import game.Transport;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

/**
 * ScotlandJadeMapHelper helps creating {@link ScotlandJadeMap ScotlandJadeMaps}.
 * It provides interfaces for {@link ScotlandJadeVertex vertices} and {@link ScotlandJadeEdge edges}
 * and default implementations for both of them.
 */
public class ScotlandJadeMapHelper {
	
	public static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger("sy.map-helper");
	
	static final int vertexCount;
	
	static final List<List<Integer>> allLinks;
	
	static final ScotlandJadeVertex[] vertices;
	
	public static final MapInfo<ScotlandJadeVertex, ScotlandJadeEdge> info;
	
	static {		
		BufferedReader in = null;
		int _vertexCount = 0;
		
		try {
			InputStream stream = ScotlandJadeMapHelper.class.getClassLoader().getResourceAsStream("SCOTMAP.TXT");
			logger.info("loading SCOTMAP.TXT " + (stream != null));
			in = new BufferedReader(new InputStreamReader(stream));
		} catch (Exception buildException) {
			logger.error("loading SCOTMAP.TXT", buildException);
			if (in != null) {
				try {
					in.close();
				} catch (IOException closeException) {
					logger.error("closing SCOTMAP.TXT", closeException);
				}
			}
		}
		
		String buffer = safeReadLine(in);
		StringTokenizer tokenizer = new StringTokenizer(buffer, " ");
		_vertexCount = Integer.parseInt(tokenizer.nextToken());	
		vertices = new ScotlandJadeVertex[_vertexCount];
		allLinks = new ArrayList<List<Integer>>(_vertexCount);
		
		// Skip 0 0
		safeReadLine(in);
		
		for (int i = 0; i < _vertexCount; ++i) {
			vertices[i] = new ScotlandJadeVertexImpl(i);
			allLinks.add(new ArrayList<Integer>(5));
		}
		
//		logger.debug("Total # of nodes: " + maxNodes);
		
		while ((buffer = safeReadLine(in)) != null) {
			tokenizer = new StringTokenizer(buffer, " ");			
			int idx = Integer.parseInt(tokenizer.nextToken());
			
			List<Integer> links = allLinks.get(idx);
			links.add(Integer.parseInt(tokenizer.nextToken()));
			int type = Transport.NONE;
			
			char t = tokenizer.nextToken().charAt(0);
			switch(t) {
			case 'T': type = Transport.TAXI; break;
			case 'B': type = Transport.BUS; break;
			case 'U': type = Transport.UG; break;
			case 'F': type = Transport.FERRY; break;
			default: 
				logger.warn("unkown transport: " + t);
			}
			
			links.add(type);
//			logger.debug("" + idx + ": " +links);
		}
		
		vertexCount	= _vertexCount;
		info = new MapInfo<ScotlandJadeVertex, ScotlandJadeEdge>(createMap());
	}
	
	/**
	 * Calls {@link RandomAccessFile#readLine()} but catches all {@link IOException} which may occur.
	 * @param in the file to be read from
	 * @return the line read or {@code null} if nothing could be read or an error occurred
	 */
	private static String safeReadLine(BufferedReader in) {
		try {
			return in.readLine();
		} catch (IOException e) {
			logger.error("read line", e);
		}
		return null;
	}
	
	
	/**
	 * Creates a map using the specified implementations for vertices and edges.
	 * @param <V> vertex type
	 * @param <E> edge type
	 * @param vf vertex factory
	 * @param ef edge factory
	 * @return the map
	 */
	public static <V extends ScotlandJadeVertex, E extends ScotlandJadeEdge> 
	ScotlandJadeMap createMap() {

		Set<ScotlandJadeEdge> edges = new HashSet<ScotlandJadeMapHelper.ScotlandJadeEdge>();
		
		for (int i = 0; i < allLinks.size(); ++i) {
			List<Integer> links = allLinks.get(i);
			for (int j = 0; j < links.size(); j += 2) {	
				try {
					edges.add(new ScotlandJadeEdgeImpl(i, links.get(j), links.get(j + 1)));
//					logger.debug("added " + source.getIndex() + " -> " + target.getIndex() + ": " + res);
				} catch (Exception e) {
					logger.error("at " + i + ", " + j, e);
				}
			}
		}
		
		return ScotlandJadeMap.create(vertexCount, edges);
	}
	
	/**
	 * Vertex interface.
	 */
	public interface ScotlandJadeVertex {
		
		enum Type { FUGITIVE, DETECTIVE, EMPTY }

		Type DEFAULT_TYPE = Type.EMPTY;
		
		public int getIndex();
		
		public Type getType();
		
		public void setType(Type t);
		
	}
	
	/**
	 * Vertex default implementation. 
	 */
	public static class ScotlandJadeVertexImpl implements ScotlandJadeVertex {
		
		int index;
		
		Type type = DEFAULT_TYPE;
		
		public ScotlandJadeVertexImpl(int index) {
			this.index = index;
		}

		@Override
		public int getIndex() {
			return index;
		}
		
		@Override
		public void setType(Type t) {
			type = t;
		}
		
		@Override
		public Type getType() {
			return type;
		}
		
		@Override
		public String toString() {
			return "(" + index + "; " + type + ")";
		}
		
		@Override
		public int hashCode() {
			return 42 + index;
		}
		
		@Override
		public boolean equals(Object obj) {
			if (obj instanceof ScotlandJadeVertexImpl) {
				ScotlandJadeVertexImpl v2 = (ScotlandJadeVertexImpl) obj;
				return v2.index == index;
			}
			return false;
		}
		
	}
	
	/**
	 * Edge interface.
	 */
	public interface ScotlandJadeEdge {
		
		public int getType();
		
		public int getSourceIndex();
		
		public int getTargetIndex();
		
		/**
		 * korrigiert den ungerichten Graph über die Mitgabe des von auszugehenden VertexIndexes.
		 * @param index
		 * @return
		 */
		public int getTargetIndex(int index);
		
	}
	
	/**
	 * Edge default implementation. 
	 */
	public static class ScotlandJadeEdgeImpl implements ScotlandJadeEdge {
		
		int type;
		
		int sourceIndex, targetIndex;
		
		public ScotlandJadeEdgeImpl(int source, int target, int type) {
			this.sourceIndex = source;
			this.targetIndex = target;
			this.type = type;
		}

		@Override
		public int getType() {
			return type;
		}
		
		@Override
		public int getSourceIndex() {
			return sourceIndex;
		}
		
		@Override
		public int getTargetIndex() {
			return targetIndex;
		}
		
		@Override
		public String toString() {
			return "{" + sourceIndex + " -> " + targetIndex + "}";
		}
		
		@Override
		public int hashCode() {
			return sourceIndex + 31 * targetIndex + type;
		}
				
		@Override
		public boolean equals(Object obj) {
			if (obj instanceof ScotlandJadeEdgeImpl) {
				ScotlandJadeEdgeImpl e2 = (ScotlandJadeEdgeImpl) obj;
				return ((e2.sourceIndex == sourceIndex && e2.targetIndex == targetIndex) ||
						(e2.sourceIndex == targetIndex && e2.targetIndex == sourceIndex)) && e2.type == type;
			}
			return false;
		}

		@Override
		public int getTargetIndex(int index) {
			if (index != sourceIndex && index != targetIndex)
				throw new IllegalArgumentException("edge '" + this + "' isn't connected to " + index);
			
			return targetIndex == index ? sourceIndex : targetIndex;
		}
		
	}

}
